﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using HPSocketCS;
using System.Runtime.InteropServices;
using System.IO;
using System.Threading;
using FileUDClassLibrary;

namespace ClientTest
{
    public partial class Frm_Client : Form
    {
        /// <summary>
        /// 发文件的状态
        /// </summary>
        private SendFileState aCurrentSendFileState = SendFileState.NoSend;

        /// <summary>
        /// 当前正在发送的文件信息
        /// </summary>
        private SendFileInfo aCurrentSendFileInfo = new SendFileInfo();

        /// <summary>
        /// 当前正在发送的文件路径
        /// </summary>
        private string aCurrentFilePath = "";

        private AppState appState = AppState.Stoped;

        private delegate void ConnectUpdateUiDelegate(int dataLength);
        private delegate void SetAppStateDelegate(AppState state);
        private delegate void ShowMsg(string msg);
        private ShowMsg AddMsgDelegate;
        /// <summary>
        /// 更新显示
        /// </summary>
        private ConnectUpdateUiDelegate UpdateUI;
        TcpPackClient aTcpClientObj = new TcpPackClient();

        /// <summary>
        /// 每次发送的最大字节数
        /// <para>取第一位用标识，所以数据最大发送4095，加上标识位就总共4096了</para>
        /// </summary>
        private int aMaxBufferLength = 4095;

        /// <summary>
        /// 当前正在使用的文件上传对象
        /// </summary>
        private FileUDInfoControl aCurrentFileUDObj = null;

        private StreamWriter aLog = null;

        public Frm_Client()
        {
            InitializeComponent();
        }

        private void Frm_Main_Load(object sender, EventArgs e)
        {
            try
            {
                aLog = new StreamWriter(@"logpg.log");

                // 加个委托显示msg,因为on系列都是在工作线程中调用的,ui不允许直接操作
                AddMsgDelegate = new ShowMsg(AddMsg);
                UpdateUI = new ConnectUpdateUiDelegate(UpdateSendFileView);
                // 设置client事件
                aTcpClientObj.OnConnect += new TcpClientEvent.OnConnectEventHandler(OnConnect);
                aTcpClientObj.OnSend += new TcpClientEvent.OnSendEventHandler(OnSend);
                aTcpClientObj.OnReceive += new TcpClientEvent.OnReceiveEventHandler(OnReceive);
                aTcpClientObj.OnClose += new TcpClientEvent.OnCloseEventHandler(OnClose);
                
                SetAppState(AppState.Stoped);

                AddMsg(string.Format("HP-Socket Version: {0}", aTcpClientObj.Version));
            }
            catch (Exception ex)
            {
                SetAppState(AppState.Error);
                AddMsg(ex.Message);
            }
        }

        private HandleResult ATcpClientObj_OnReceive(TcpPullClient sender, int length)
        {
            throw new NotImplementedException();
        }

        void ConnectUpdateUi()
        {
            //异步连接的更新UI
        }

        HandleResult OnConnect(TcpClient sender)
        {
            // 已连接 到达一次

            // 如果是异步联接,更新界面状态
            //this.Invoke(new ConnectUpdateUiDelegate(ConnectUpdateUi));

            AddMsg(string.Format(" > [{0},OnConnect]", sender.ConnectionId));

            return HandleResult.Ok;
        }

        HandleResult OnSend(TcpClient sender, byte[] bytes)
        {
            // 客户端发数据了
            AddMsg(string.Format(" > [{0},OnSend] -> ({1} bytes)", sender.ConnectionId, bytes.Length));
            return HandleResult.Ok;
        }

        HandleResult OnReceive(TcpClient sender, byte[] bytes)
        {
            try
            {
                byte iOneByte = bytes[0];
                switch(iOneByte)
                {
                    case 1:
                        {
                            if (aCurrentSendFileState != SendFileState.Handshake) return HandleResult.Ignore;

                            aCurrentSendFileState = SendFileState.Sending;
                            SendFileToServer_RFBP(aCurrentFilePath);
                            break;
                        }
                    case 3:
                        {
                            if (aCurrentSendFileState != SendFileState.Sending) return HandleResult.Ignore;

                            byte[] idatas = new byte[bytes.Length - 1];
                            Array.Copy(bytes, 1, idatas, 0, bytes.Length - 1);
                            string iFilePath = Encoding.Unicode.GetString(idatas);
                            AddMsg(string.Format(" > [{0},OnReceive] -> 接收到完成传输指令：文件路径：{1}", sender.ConnectionId, iFilePath));
                            aCurrentFileUDObj.FileState = FileState.FINISH;
                            aCurrentSendFileState = SendFileState.NoSend;
                            aCurrentFilePath = "";
                            StartSendFile();
                            break;
                        }
                }

                return HandleResult.Ok;
            }
            catch(Exception Err)
            {
                AddMsg(string.Format(" > [{0},OnReceive] -> 发生错误：{1}", sender.ConnectionId, Err.Message));
                return HandleResult.Ignore;
            }
        }

        HandleResult OnClose(TcpClient sender, SocketOperation enOperation, int errorCode)
        {
            if (errorCode == 0)
                // 连接关闭了
                AddMsg(string.Format(" > [{0},OnClose]", sender.ConnectionId));
            else
                // 出错了
                AddMsg(string.Format(" > [{0},OnError] -> OP:{1},CODE:{2}", sender.ConnectionId, enOperation, errorCode));

            btn_Start.Text = "连  接";
            // 通知界面,只处理了连接错误,也没进行是不是连接错误的判断,所以有错误就会设置界面
            // 生产环境请自己控制
            //this.Invoke(new SetAppStateDelegate(SetAppState), AppState.Stoped);

            return HandleResult.Ok;
        }

        /// <summary>
        /// 设置程序状态
        /// </summary>
        /// <param name="state"></param>
        void SetAppState(AppState state)
        {
            appState = state;
        }

        /// <summary>
        /// 往listbox加一条项目
        /// </summary>
        /// <param name="msg"></param>
        void AddMsg(string msg)
        {
            if (lis_Msg.InvokeRequired)
            {
                lis_Msg.Invoke(AddMsgDelegate, msg);
            }
            else
            {
                if (lis_Msg.Items.Count > 100)
                {
                    using (StreamWriter iLogFileObj = new StreamWriter(@"log.txt", true, Encoding.Unicode))
                    {
                        foreach (object iobj in lis_Msg.Items)
                        {
                            iLogFileObj.WriteLine(iobj.ToString());
                        }
                    }

                    lis_Msg.Items.Clear();
                }
                lis_Msg.Items.Add(msg);
            }
        }

        private void btn_Start_Click(object sender, EventArgs e)
        {
            try
            {
                if (appState != AppState.Started)
                {
                    String ip = this.txt_IP.Text.Trim();
                    ushort port = ushort.Parse(this.txt_Port.Text.Trim());

                    // 写在这个位置是上面可能会异常
                    SetAppState(AppState.Starting);

                    AddMsg(string.Format("$Client Starting ... -> ({0}:{1})", ip, port));

                    if (aTcpClientObj.Connect(ip, port, false))
                    {
                        SetAppState(AppState.Started);
                        AddMsg(string.Format("$Client Start OK -> ({0}:{1})", ip, port));
                        btn_Start.Text = "断  开";
                        //PGBar_Main.Value = 0;
                    }
                    else
                    {
                        SetAppState(AppState.Stoped);
                        throw new Exception(string.Format("$Client Start Error -> {0}({1})", aTcpClientObj.ErrorMessage, aTcpClientObj.ErrorCode));
                    }
                }
                else
                {
                    // 停止服务
                    AddMsg("$Server Stop");
                    if (aTcpClientObj.Stop())
                    {
                        SetAppState(AppState.Stoped);
                        btn_Start.Text = "连  接";
                    }
                    else
                    {
                        AddMsg(string.Format("$Stop Error -> {0}({1})", aTcpClientObj.ErrorMessage, aTcpClientObj.ErrorCode));
                    }
                }
            }
            catch (Exception ex)
            {
                AddMsg(ex.Message);
            }
        }

        private void btn_SendFile_Click(object sender, EventArgs e)
        {
            if (flowLayoutPanel1.Controls.Count == 0)
            {
                MessageBox.Show("请选择需要上传的文件", "提示");
                return;
            }
            StartSendFile();
        }

        private bool SendFileToServer_RFBP(string FilePath)
        {
            if (!File.Exists(FilePath)) return false;
            FileStream iFileObj = new FileStream(FilePath, FileMode.Open, FileAccess.Read);
            long iFileSize = iFileObj.Length;
            if (iFileSize == 0) return false;

            if (iFileSize < aMaxBufferLength)
            {
                byte[] idatas = new byte[iFileSize + 1];
                idatas[0] = 2;
                iFileObj.Read(idatas, 1, (int)iFileSize);
                if(aTcpClientObj.Send(idatas, idatas.Length))
                    UpdateSendFileView((int)iFileSize);
            }
            else
            {
                long iLeftLength = iFileSize;
                int iReadLength = 0;
                byte[] idatas;
                while (iLeftLength != 0)
                {

                    if (iLeftLength < aMaxBufferLength)
                    {
                        idatas = new byte[iLeftLength + 1];
                        iReadLength = iFileObj.Read(idatas, 1, (int)iLeftLength);
                    }
                    else
                    {
                        idatas = new byte[aMaxBufferLength + 1];
                        iReadLength = iFileObj.Read(idatas, 1, aMaxBufferLength);
                    }
                    idatas[0] = 2;
                    if(aTcpClientObj.Send(idatas, idatas.Length))
                        UpdateSendFileView(iReadLength);
                    iLeftLength -= iReadLength;
                }
            }

            return true;
        }

        private void flowLayoutPanel1_DragDrop(object sender, DragEventArgs e)
        {
            string[] iFilePaths = (string[])e.Data.GetData(DataFormats.FileDrop);
            foreach(string iFilePath in iFilePaths)
            {
                FileInfo iFileInfoObj = new FileInfo(iFilePath);
                FileUDInfoControl iFileUDObj = new FileUDInfoControl();
                iFileUDObj.FilePath = iFilePath;
                iFileUDObj.FileSize = iFileInfoObj.Length;

                flowLayoutPanel1.Controls.Add(iFileUDObj);
            }
        }

        private void flowLayoutPanel1_DragEnter(object sender, DragEventArgs e)
        {
            e.Effect = DragDropEffects.All;
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                e.Effect = DragDropEffects.All;
            }
            else
            {
                e.Effect = DragDropEffects.None;
            }
        }

        private void groupBox2_DragEnter(object sender, DragEventArgs e)
        {
            e.Effect = DragDropEffects.All;
        }

        private void btn_addFile_Click(object sender, EventArgs e)
        {
            if (openFileDialog1.ShowDialog() != DialogResult.OK) return;
            string iFilePath = openFileDialog1.FileName;
            FileInfo iFileInfoObj = new FileInfo(iFilePath);
            FileUDInfoControl iFileUDObj = new FileUDInfoControl();
            iFileUDObj.FilePath = iFilePath;
            iFileUDObj.FileSize = iFileInfoObj.Length;

            flowLayoutPanel1.Controls.Add(iFileUDObj);
        }

        private void StartSendFile()
        {
            foreach (FileUDInfoControl iFileUDObj in flowLayoutPanel1.Controls)
            {
                if (iFileUDObj.FileState == FileState.WAIT)
                {
                    aCurrentFileUDObj = iFileUDObj;
                    aCurrentFileUDObj.FileState = FileState.UPING;
                    FileInfo iFileInfo = new FileInfo(aCurrentFileUDObj.FilePath);
                    aCurrentSendFileInfo = new SendFileInfo();
                    aCurrentSendFileInfo.FileName = iFileInfo.Name;
                    aCurrentSendFileInfo.FileSize = iFileInfo.Length;
                    aLog.WriteLine("总字节数：" + aCurrentSendFileInfo.FileSize.ToString());
                    aCurrentFilePath = aCurrentFileUDObj.FilePath;

                    aCurrentSendFileState = SendFileState.Handshake;
                    byte[] iObjBytes = aTcpClientObj.StructureToByte(aCurrentSendFileInfo);
                    byte[] iSendBytes = new byte[iObjBytes.Length + 1];
                    iSendBytes[0] = 1;
                    Array.Copy(iObjBytes, 0, iSendBytes, 1, iObjBytes.Length);

                    aTcpClientObj.Send(iSendBytes, iSendBytes.Length);

                    return;
                }
            }
        }

        private void UpdateSendFileView(int datalength)
        {
            if (aCurrentFileUDObj == null) return;
            if (aCurrentSendFileState != SendFileState.Sending) return;
            if (aCurrentFileUDObj.InvokeRequired)
            {
                aCurrentFileUDObj.Invoke(UpdateUI, datalength);
            }
            else
            {
                aCurrentFileUDObj.Value += datalength;
                aLog.WriteLine("发送字节数：" + datalength.ToString());
            }
        }

        private void Frm_Client_FormClosing(object sender, FormClosingEventArgs e)
        {
            if(aLog!=null)
            {
                aLog.Flush();
                aLog.Close();
                aLog.Dispose();
            }
        }
    }

    /// <summary>
    /// 文件信息
    /// </summary>
    public struct SendFileInfo
    {
        /// <summary>
        /// 文件名称
        /// </summary>
        [MarshalAs(UnmanagedType.ByValTStr,SizeConst =255)]
        public string FileName;

        /// <summary>
        /// 文件大小
        /// </summary>
        public long FileSize;
    }

    public enum AppState
    {
        Starting, Started, Stoping, Stoped, Error
    }

    /// <summary>
    /// 发送文件状态
    /// </summary>
    public enum SendFileState
    {
        /// <summary>
        /// 不是在发文件
        /// </summary>
        NoSend,
        /// <summary>
        /// 握手
        /// </summary>
        Handshake,
        /// <summary>
        /// 可以发送
        /// </summary>
        CanSend,
        /// <summary>
        /// 正在发送
        /// </summary>
        Sending
    }
}
